#include <iostream>
#include "Types.hpp"
#include "Debug.hpp"
#include "FileReader.hpp"
#include "Simulator.hpp"
#include <vector>
#include <fstream>
#include <string>
#include <cmath>
#include <iomanip>
#include <sys/time.h>

//OpenGL toolkit
#include<GL/glut.h>

//Global pointer to the simulator
Simulator* sim;
//Buffer for writing
char *texto = new char[512];
//Depth of the image
real depth =-380;

//Display dimensions
const real xDmin=0.0;
const real yDmin=0.0;
const real zDmin=0.0;

const real lengthDx=100;
const real lengthDy=100;
const real lengthDz=100;

const real xDmax=xDmin+lengthDx;
const real yDmax=yDmin+lengthDy;
const real zDmax=zDmin+lengthDz;

const real border=3.0;

void getColor3f(double mag,double larger,real largerInverse,real& red,real& green,real& blue)
{
     double mayorV=larger;

     if(mag<mayorV*0.3333333)
     {
           red=0;
           green=mag*3*largerInverse;
           blue=1;
     }
     else if(mag>=mayorV*0.333333 && mag<0.666666666*mayorV)
     {
           red=0;
           green=1;
           blue=2-3*mag*largerInverse;
     }
     else
     {
           red=-2+3*mag*largerInverse;
           green=1;
           blue=0;
     }
}

void handleKeypress(unsigned char key, int x, int y)
{
     switch (key)
     {
            case 27:// tecla esc
                 exit(0);
                 break;
     }
}

void initRendering()
{
    glEnable(GL_DEPTH_TEST);//Allows drawing images in front of others
}

void writeText(real x, real y, real esc)
{
    glPushMatrix();
    glTranslated(x,y,0);
    glScaled(esc,esc,esc);
    glLineWidth(0.5);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    for (int i = 0; texto[i]!= '\0'; ++i)
    {
           glutStrokeCharacter(GLUT_STROKE_ROMAN,texto[i]);
    }
    glPopMatrix();
}

void drawParticle(real x, real y, real z, real radius)
{
    glPushMatrix();
    glTranslated(x,y,z);
    //glutSolidTeapot(125.0);
    glutWireSphere(radius,10,10);
    glPopMatrix();
}

void drawCubeDomain(real x1,real y1, real z1,real x2,real y2, real z2)
{
    glBegin(GL_LINES);
    glVertex3f(x1,y1,z1);
    glVertex3f(x2,y1,z1);
    glVertex3f(x2,y1,z1);
    glVertex3f(x2,y2,z1);
    glVertex3f(x2,y2,z1);
    glVertex3f(x1,y2,z1);
    glVertex3f(x1,y1,z1);
    glEnd();
    glBegin(GL_LINES);
    glVertex3f(x2,y1,z1);
    glVertex3f(x2,y1,z2);
    glVertex3f(x2,y1,z2);
    glVertex3f(x2,y2,z2);
    glVertex3f(x2,y2,z2);
    glVertex3f(x2,y2,z1);
    glVertex3f(x2,y1,z1);
    glEnd();
    glBegin(GL_LINES);
    glVertex3f(x2,y2,z2);
    glVertex3f(x1,y2,z2);
    glVertex3f(x1,y2,z2);
    glVertex3f(x1,y1,z2);
    glVertex3f(x1,y1,z2);
    glVertex3f(x2,y1,z2);
    glVertex3f(x2,y2,z2);
    glEnd();
    glBegin(GL_LINES);
    glVertex3f(x2,y2,z2);
    glVertex3f(x1,y2,z2);
    glVertex3f(x1,y2,z2);
    glVertex3f(x1,y2,z1);
    glVertex3f(x1,y2,z1);
    glVertex3f(x2,y2,z1);
    glEnd();

    glBegin(GL_LINES);
    glVertex3f(x1,y2,z2);
    glVertex3f(x1,y2,z1);
    glVertex3f(x1,y2,z1);
    glVertex3f(x1,y1,z1);
    glVertex3f(x1,y1,z1);
    glVertex3f(x1,y1,z2);
    glEnd();

}

void writeParticles()
{
    glPushMatrix();
    real m=15,s=65;
    glRotatef(m,1,0,0);
    glRotatef(s,0,1,0);
    /*real i,j,k;
    for(i=xDmin;i<=xDmax;i+=lengthDx*0.1)
    {
       for(j=yDmin;j<=yDmax;j+=lengthDy*0.1)
       {
           for(k=zDmin;k<=zDmax;k+=lengthDz*0.1)
           {
               drawParticle(i,j,k,1);
           }
       }

    }*/
    int N = sim->NumParticles();
    cl_real Scalex=lengthDx/sim->lengthx();
    cl_real Scaley=lengthDy/sim->lengthy();
    cl_real Scalez=lengthDz/sim->lengthz();

    cl_real deltaDx=sim->xmin()-xDmin;
    cl_real deltaDy=sim->ymin()-yDmin;
    cl_real deltaDz=sim->zmin()-zDmin;

    for(int i=0;i<N;++i)
    {
        cl_real4 pos=sim->position(i);
        real x=(pos.s[0]-deltaDx)*Scalex;
        real y=(pos.s[1]-deltaDy)*Scaley;
        real z=(pos.s[2]-deltaDz)*Scalez;
        drawParticle(x,y,z,3);
        //std::cout<<"Scaled "<<x<<" "<<y<<" "<<z<<"\n";
        //std::cout<<"Real "<<pos.s[0]<<" "<<pos.s[1]<<" "<<pos.s[2]<<"\n";
    }
    //std::cout<<"\n\n";
    drawCubeDomain(xDmin-border,yDmin-border,zDmin-border,xDmax+border,yDmax+border,zDmax+border);
    glPopMatrix();
}

void pintarCirculo(real radio,real x0,real y0,real z0)
{
     real angulo;
     glBegin(GL_POLYGON);
     for(real i=0;i<2*3.14159;i=i+0.01)
     {
              angulo=i*2*3.14159;
              glVertex3f(x0+radio*cos(angulo),y0+radio*sin(angulo),z0);
     }
     glEnd();

}

//It is called when the dimension of the window is changed
void handleResize(int w, int h)
{
    //Convert from pixels to coordinates
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    //Set up perspective
    glLoadIdentity(); //Reset the camera
    gluPerspective(45.0,                  //angle -distance from camera
      (double)w / (double)h, //Ratio between width and height
      1.0,                   //closer z coordinate
      500.0);                //farther z coordinate
}



void drawScene()
{
     //Clean the scene
     glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
     glClearColor(1,1,1,1); // Color background
     glMatrixMode(GL_MODELVIEW); //Cambia a la perspectiva de dibujo
     glLoadIdentity(); //Resetea la perspectiva de dibujo
     glTranslated(0.0,0.0,depth);
     real tam = 0.1;
     glColor3f(0,0,0);
     sprintf(texto,"Smoothing_Particle_Hydrodynamics" );
     writeText(-100.0,140.0,tam);

     glPushMatrix();
     glTranslated(-50.0,-50.0,0.0);
     writeParticles();
     glPopMatrix();
     sim->runWithGraphics(5);
     glutSwapBuffers(); //Envia la escena 3D a la pantalla
}

void update(int value)
{
    //Ordena que la escena se desliegue nuevamente
    glutPostRedisplay();

    //Vuelve a llamar esta funcion en tantos milisegundos como se le haya
    //indicado en el primer par�tro
    glutTimerFunc(1, update, value);
}

int main(int argc, char **argv)
{

	if(argc!=2){
		ERROR("Please Type the name of the input file");
		return 1;
	}
	std::string filename=argv[1];


    /*   // Asserting the Cut Off Radius to be Smaller than the Minimum of the Cell Sizes
    if(!(cut_r<=len_x && cut_r<=len_y && cut_r<=len_z)){
        std::cerr<<"Cut-Off Radius is greater than the Cell Size- Exiting";
        return EXIT_FAILURE;
    }*/

    //Init simulator
    sim=new Simulator(filename);
    sim->QueryPlatformDevice("kernels.cl");
    sim->MemoryBuffersAllocation();
    sim->setKernels(1);
    /*for(int sa=1;sa<5;++sa){
    sim->runWithGraphics(sa);
    for(int i=0;i<sim->NumParticles();++i)
    {
        cl_real4 pos=sim->position(i);
	cl_real4 vel=sim->velocity(i);
        cl_real den = sim->density(i);
	cl_real pre=sim->pressure(i);
	std::cout<<"Real "<<pos.s[0]<<" "<<pos.s[1]<<" "<<pos.s[2]<<"  "
		 <<vel.s[0]<<"  "<<vel.s[1]<<"  "<<vel.s[2]<<"  "
		 <<den<<"  "<<pre<<"\n";
    }
    std::cout<<"\n\n";
    }*/
    //Inicializa GLUT
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    glutInitWindowSize(1200, 900); //Size of the window

    //Crea una nueva ventana
    glutCreateWindow("INDIA RULES");
    initRendering(); //Inicialize rendering

    //Establece las funciones encargadas de dibujar la escena, reaccionar cuando
    //se presione una tecla y cuando la ventana cambie de tamano.
    glutDisplayFunc(drawScene);
    glutKeyboardFunc(handleKeypress);
    glutReshapeFunc(handleResize);

    //Evento del Mouse
    //glutMouseFunc(mouseButton);
    //glutMotionFunc(mouseMove);

     glutPostRedisplay();


    //Llama por primera vez al metodo update
    glutTimerFunc(0, update, 0);
    glutMainLoop(); //Comienza el loop principal

    sim->finishQueue();
    delete sim;
    delete [] texto;
	return 0;
}

double timeStamp()
{
    struct timeval tp;
    gettimeofday(&tp, NULL);
    return((double)(tp.tv_sec + tp.tv_usec/1000000.0));
}

